/*
 * PluginAction.java 26 oct. 2008
 *
 * Sweet Home 3D, Copyright (c) 2008 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.plugin;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import com.eteks.sweethome3d.tools.ResourceURLContent;

/**
 * An action made available to application users through a plugin.
 * @author Emmanuel Puybaret
 */
public abstract class PluginAction
{
	/**
	 * Enumeration of the various properties this action may define.
	 */
	public enum Property
	{
		/** 
		 * The key of the property of <code>String</code> type that specifies
		 * the name of an action, used for a menu or button.
		 */
		NAME,
		/**
		 * The key of the property of <code>String</code> type that specifies
		 * a short description of an action, used for tool tip text.
		 */
		SHORT_DESCRIPTION,
		/**
		 * The key of the property of <code>Content</code> type that specifies
		 * an image content of an action, used for tool bar buttons.  
		 */
		SMALL_ICON,
		/**
		 * The key of the property of <code>Character</code> type that specifies 
		 * the ASCII character used as the mnemonic of an action.
		 */
		MNEMONIC,
		/**
		 * The key of the property of <code>Boolean</code> type that specifies
		 * if an action will appear in the main tool bar.
		 */
		TOOL_BAR,
		/**
		 * The key of the property of <code>String</code> type that specifies
		 * in which menu of the main menu bar an action should appear. 
		 */
		MENU,
		/**
		 * The key of the property of <code>Boolean</code> type that specifies
		 * if an action is enabled or not.
		 */
		ENABLED,
	}
	
	private final Map<Property, Object> propertyValues = new HashMap<Property, Object>();
	private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
	
	/**
	 * Creates a disabled plug-in action.
	 */
	public PluginAction()
	{}
	
	/**
	 * Creates a disabled plug-in action with properties retrieved from a resource bundle 
	 * in which key starts with <code>actionPrefix</code>.
	 * <br>For example, a plug-in action created by the call
	 * <code>new PluginAction("com.mycompany.mypackage.MyResources", "EXPORT", plugin.getPluginClassLoader())</code> 
	 * will retrieve its property values from the <code>/com/mycompany/mypackage/MyResources.properties</code> file
	 * bundled with the plug-in class, and this file may describe the action <code>EXPORT</code> 
	 * with the following keys: 
	 * <pre> EXPORT.NAME=Export
	 * EXPORT.SHORT_DESCRIPTION=Export home data
	 * EXPORT.SMALL_ICON=/com/mycompany/mypackage/resources/export.png
	 * EXPORT.MNEMONIC=X
	 * EXPORT.IN_TOOL_BAR=true
	 * EXPORT.MENU=File</pre>
	 * @param resourceBaseName the base name of a resource bundle  
	 * @param actionPrefix  prefix used in resource bundle to search action properties
	 * @param pluginClassLoader the class loader that will be used to search the resource bundle   
	 * @throws MissingResourceException if no resource bundle could be found from 
	 *    <code>resourceBaseName</code>.
	 */
	public PluginAction(String resourceBaseName, String actionPrefix, ClassLoader pluginClassLoader)
	{
		this(resourceBaseName, actionPrefix, pluginClassLoader, false);
	}
	
	/**
	 * Creates an action with properties retrieved from a resource bundle 
	 * in which key starts with <code>actionPrefix</code>.
	 * @param resourceBaseName the base name of a resource bundle
	 * @param actionPrefix  prefix used in resource bundle to search action properties
	 * @param pluginClassLoader the class loader that will be used to search the resource bundle   
	 * @param enabled <code>true</code> if the action should be enabled at creation.
	 * @throws MissingResourceException if no resource bundle could be found from 
	 *    <code>resourceBaseName</code>.
	 */
	public PluginAction(String resourceBaseName, String actionPrefix, ClassLoader pluginClassLoader, boolean enabled)
	{
		readActionPropertyValues(resourceBaseName, actionPrefix, pluginClassLoader);
		setEnabled(enabled);
	}
	
	/**
	 * Reads the properties of this action from a resource bundle of given base name.
	 * @throws MissingResourceException if no resource bundle could be found from 
	 *    <code>resourceBaseName</code>.
	 */
	private void readActionPropertyValues(String resourceBaseName, String actionPrefix, ClassLoader pluginClassLoader)
	{
		ResourceBundle resource;
		if (pluginClassLoader != null)
		{
			resource = ResourceBundle.getBundle(resourceBaseName, Locale.getDefault(), pluginClassLoader);
		}
		else
		{
			resource = ResourceBundle.getBundle(resourceBaseName, Locale.getDefault());
		}
		String propertyPrefix = actionPrefix + ".";
		putPropertyValue(Property.NAME, getOptionalString(resource, propertyPrefix + Property.NAME));
		putPropertyValue(Property.SHORT_DESCRIPTION,
				getOptionalString(resource, propertyPrefix + Property.SHORT_DESCRIPTION));
		String smallIcon = getOptionalString(resource, propertyPrefix + Property.SMALL_ICON);
		if (smallIcon != null)
		{
			if (smallIcon.startsWith("/"))
			{
				smallIcon = smallIcon.substring(1);
			}
			putPropertyValue(Property.SMALL_ICON, new ResourceURLContent(pluginClassLoader, smallIcon));
		}
		String mnemonicKey = getOptionalString(resource, propertyPrefix + Property.MNEMONIC);
		if (mnemonicKey != null)
		{
			putPropertyValue(Property.MNEMONIC, Character.valueOf(mnemonicKey.charAt(0)));
		}
		String toolBar = getOptionalString(resource, propertyPrefix + Property.TOOL_BAR);
		if (toolBar != null)
		{
			putPropertyValue(Property.TOOL_BAR, Boolean.valueOf(toolBar));
		}
		putPropertyValue(Property.MENU, getOptionalString(resource, propertyPrefix + Property.MENU));
	}
	
	/**
	 * Returns the value of <code>propertyKey</code> in <code>resource</code>, 
	 * or <code>null</code> if the property doesn't exist.
	 */
	private String getOptionalString(ResourceBundle resource, String propertyKey)
	{
		try
		{
			return resource.getString(propertyKey);
		}
		catch (MissingResourceException ex)
		{
			return null;
		}
	}
	
	/**
	 * Adds the property change <code>listener</code> in parameter to this plugin action.
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener)
	{
		this.propertyChangeSupport.addPropertyChangeListener(listener);
	}
	
	/**
	 * Removes the property change <code>listener</code> in parameter from this plugin action.
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener)
	{
		this.propertyChangeSupport.removePropertyChangeListener(listener);
	}
	
	/**
	 * Returns a property value of this action.
	 */
	public Object getPropertyValue(Property property)
	{
		return this.propertyValues.get(property);
	}
	
	/**
	 * Sets a property value of this action, and fires a <code>PropertyChangeEvent</code>
	 * if the value changed. 
	 */
	public void putPropertyValue(Property property, Object value)
	{
		Object oldValue = this.propertyValues.get(property);
		if (value != oldValue || (value != null && !value.equals(oldValue)))
		{
			this.propertyValues.put(property, value);
			this.propertyChangeSupport.firePropertyChange(property.name(), oldValue, value);
		}
		
	}
	
	/**
	 * Sets the enabled state of this action. When enabled, any menu item or tool bar button 
	 * associated with this object is enabled and able to call the <code>execute</code> method.
	 * If the value has changed, a <code>PropertyChangeEvent</code> is sent
	 * to listeners. By default, an action is disabled.
	 */
	public void setEnabled(boolean enabled)
	{
		putPropertyValue(Property.ENABLED, enabled);
	}
	
	/**
	 * Returns the enabled state of this action. 
	 * @return <code>true</code> if this action is enabled.
	 */
	public boolean isEnabled()
	{
		Boolean enabled = (Boolean) getPropertyValue(Property.ENABLED);
		return enabled != null && enabled.booleanValue();
	}
	
	/**
	 * Executes this action. This method will be called by application when the user
	 * wants to execute this action.
	 */
	public abstract void execute();
}
